/* 
SolutionSet JavaScript Library, requires Prototype version 1.6.0
Copyright (c) 2008, SolutionSet, All rights reserved.
Code licensed under the MIT License:
https://solutionset-javascript.googlecode.com/svn/trunk/docs/license.txt
*/

/**
 * Prototype_SS
 *
 * @module
 * @requires prototype, prototype_ss
 * @optional animation, connection, get
 * @namespace Solution.core
 * @title Prototype_SS
 */
/**
 * Prototype_SS 
 * Prototype_SS include extensions and patches to Prototype objects and 
 * creates the Cookie object that can grab and set a cookie by name.
 * @see Prototype.js
 * prototype_ss.js 
 * Requires: Prototype.js 1.6.0
 */
if (typeof(Solution) == 'undefined' || !Solution) {
	var Solution = {};
}

/**
 * Returns the namespace specified and creates it if it doesn't exist
 * This is borrowed heavily from the YAHOO.namespace function
 * Example:
 * Solution.namespace('Solutoin.util'); // ensures that the util namespace exists
 *
 * @method namespace
 * @static
 * @param  {String*} arguments 1-n namespaces to create 
 * @return {Object}  A reference to the last namespace object created
 */
Solution.namespace = function() {
	var argLen = arguments.length;
    for (var i = 0; i  < argLen; i = i + 1) {
        var namespace = arguments[i].split(".");
		var namespaceLen = namespace.length;
		var obj = Solution;
        for (var j = (namespace[0] == "Solution") ? 1 : 0; j < namespaceLen; j = j+1) {
            obj[namespace[j]] = obj[namespace[j]] || {};
            obj = obj[namespace[j]];
        }
    }
    return obj;
};
Solution.namespace('Solution.util');

/**
 * Utilities for cookie management
 * @namespace Solution.util
 * @module cookie
 */
Solution.util.Cookie = {
	/**
	 * Gets the value of a cookie by name
	 *
	 * @param String name
	 * @return String
	 */
	get: function(name) {
		var val = String(document.cookie).replace(new RegExp("^(.*; )?"+name+"\=([^;]*)(;)?.*$"), "$2");
		return (val == document.cookie)? null : unescape(val);
	},
	
	/**
	 * Set the value of a cookie by name
	 * Example:
	 * var myDate = new Date();
	 * myDate.setFullYear(2010,0,14);
	 * Solution.core.Cookie.set('name', 'value', myDate);
	 *
	 * @param String name
	 * @param String value
	 * @param Date expDate must be an instance of Date
	 * @param String domain
	 * @return void
	 */
	set: function(name, value, expDate, domain) {
		document.cookie = name+"="+escape(value) + ";expires="+expDate.toGMTString() + (domain ? ';domain=' + domain : '');
	},
	
	/**
	 * Removes a cookie by name
	 *
	 * @param String name
	 * @return void
	 */
	remove: function(name) {
		if(this.set){
		this.set(name, '', new Date(Date.parse('Thu, 01-Jan-1970 00:00:01 GMT')));
	}}
};

/* String.prototype
---------------------------------------*/
Object.extend(String.prototype, {
	/**
	 * Extends the .substring() method to allow negative numbers to
	 * reference indices from the end of the string rather than the beginning.
	 *
	 * @param Number start
	 * @param Number end
	 * @return String
	 */
	substring: String.prototype.substring.wrap(
		function(proceed, start, end) {
			if (start < 0) start = this.length + start;
			if (end < 0) end = this.length + end;
			if (end !== 0 && !end) end = this.length;
			return proceed(start, end);
		}
	),
	
	/**
	 * Turns a plain string in to DOM Nodes
	 *
	 * @return Array
	 */
	toDOMNodes: function() {
		return new Element('div').update(this).childElements();
	}
});

Object.extend(Object, {
	/**
	 * Is Numeric checks if an object is a Number or String of just numbers. Prototype has an Object.isNumber function which will only return true if the object is an instance of Number.
	 *
	 * @param unknown object
	 * @return boolean
	 * @see Object.isNumber
	 */
	isNumeric: function(object) {
		if (this.isNumber(object))
			return true;
		if(parseInt(object) == object)
			return true;
		return false;
	},
	
	/**
	 * Converts a UNIX timestamp into a JS Date object
	 *
	 * @param String timestamp
	 * @return Date
	 */
	toDate: function (timestamp) {
		var time = new Date();
		time.setTime(parseInt(timestamp)*1000);
		return time;
	},
	
	/**
	 * Checks if the given variable is an object
	 * 
	 * @param mixed object
	 * @return bool
	 */
	isObject: function(object) {
		if (object && object.toString() == "[object Object]") {
			return true;
		} else {
			return false;
		}
	},
	
	/**
	 * Checks if the given object is an instance of Date containing a valid date.
	 * @return bool
	 */
	isValidDate: function(date) {
		return (date instanceof Date && date.toString() != 'Invalid Date' && date.toString() != 'NaN');
	},
	
	/**
	 * Takes methods and properties from source object and adds them to destination object.
	 * @param Object destination the object to exted
	 * @param Object source the object to take methods from
	 * @param bool recursive true will recirsively extend the object
	 * @return Object
	 */
	extend: function(destination, source, recursive) {
		if (recursive !== true) {
			for (var property in source)
				destination[property] = source[property];
		} else if (Object.isObject(source)) {
			if (destination) {
				for (var property in source) {
					if (destination[property]) {
						destination[property] = Object.extend(destination[property], source[property], recursive);
					} else {
						destination[property] = Object.extend({}, source[property], recursive);
					}
				}
			} else {
				destination = Object.extend({}, source);
			}
		} else if (Object.isArray(source)) {
			destination = [];
			for (var i=0, length=source.length; i<length; i++)
				destination[i] = Object.extend({}, source[i], recursive);
		} else {
			destination = source;
		}
		return destination;
	},
	
	/**
	 * Extends the Object.clone function to use recursive extension
	 * @param Object object
	 * @return Object
	 */
	clone: function(object, recursive) {
		return Object.extend({}, object, recursive);
	}
});

/* Class
---------------------------------------*/
/**
 * Extends the Class.Methods.addMethods function to make it recursively add settings
 * @param Function source
 * @return null
 */
Class.Methods.addMethods = function(source) {
	var ancestor   = this.superclass && this.superclass.prototype;
	var properties = Object.keys(source);
	
	if (!Object.keys({ toString: true }).length)
		properties.push("toString", "valueOf");
	
	for (var i = 0, length = properties.length; i < length; i++) {
		if(typeof(source) == "undefined") return;
		var property = properties[i], value = source[property];
		if (ancestor && Object.isFunction(value) &&
		value.argumentNames().first() == "$super") {
			var method = value, value = Object.extend((function(m) {
				return function() { return ancestor[m].apply(this, arguments) };
			})(property).wrap(method), {
				valueOf:  function() { return method },
				toString: function() { return method.toString() }
			});
		}
		if (Object.isObject(value)) {
			this.prototype[property] = Object.extend(Object.clone(this.prototype[property]), value, true);
		} else {
			this.prototype[property] = value;
		}
	}
	
	return this;
}

/* Element
---------------------------------------*/
Object.extend(Element.Methods, {
	/**
	 * Finds if the element or any parent element has a CSS display of none
	 *
	 * @param DOMElement element
	 * @return bool
	 */
	displayed: function(element) {
		element = $(element);
		return [element].concat(element.ancestors()).all(function(element){
			if(['none',null].indexOf($(element).getStyle('display')) == -1)
				return true;
		});
	},
	
	/**
	 * Finds the highest z-index of the current element or any parent element
	 *
	 * @param DOMElement element
	 * @return Number
	 */
	positionedZIndex: function (element) {
		element = $(element);
		return [element].concat(element.ancestors()).max(function(v){
			return v.getStyle('zIndex');
		});
	},
	
	/**
	 * Parses the classname looking for a JSON string and returns the object if it is found. returns false if no valid JSON string is found in the class
	 *
	 * @param DOMElement element
	 * @return Object
	 */
	getClassObject: function (element) {
		var vals = String(element.className).split(' ');
		for(var x=0, len=vals.length; x<len; x++){
			if(vals[x].isJSON()){
				return vals[x].evalJSON();
			}
		}
		return false;
	}
});

/* Event
---------------------------------------*/
Object.extend(Event, {
	/**
	 * Event.observe now returns an ID which can be sent to Event.unObserve to terminate that event handler.
	 * @var Array
	 */
	registry: [],
	
	/**
	 * @var int
	 */
	id: 0,
	
	/**
	 * Returns the keycode related to an event, browser-independent
	 * @param DOMEvent e
	 * @return int
	 */
	keyCode: function(e) {
		if (typeof(e.which) == 'undefined') return e.keyCode;
		return Math.max(e.which, e.keyCode);
	},
	
	/**
	 * Fires the event in the given context
	 * @param DOMEvent e
	 * @param DOMElement element
	 * @return int
	 */
	dispatch: function(e, element) {
		element = $(element);
		if (!element.dispatchEvent) {
			var type = e.type;
			return element.fireEvent('on' + type, e);
		} else {
			// TODO: this doesn't work in FireFox for some reason
			return element.dispatchEvent(e);
		}
	},

	/**
	 * Extends prototypes Event.observe function to capture all observed events in an array
	 *
	 * @param DOMElement element
	 * @param string eventName
	 * @param function handler
	 * @param bool useCapture [optional]
	 * @return object
	 */
	observe: Event.observe.wrap(
		function(proceed, element, name, observer, useCapture) {
			var regEntry = {
				id: Event.id++,
				element: element,
				name: name,
				observer: observer,
				useCapture: useCapture
			};
			Event.registry.push(regEntry);
			proceed(element, name, observer, useCapture);
			return regEntry;
		}
	),

	/**
	 * Uses the registry to stop observing a specific event
	 * Example:
	 * var eventId = $('id').observe('click', myFunction);
	 * $('id').unObserve('eventId');
	 *
	 * @param object regEntry
	 * @return bool
	 */
	unObserve: Event.stopObserving.wrap(
		function(proceed, regEntry) {
			Event.registry.find(function(eve, key){
				if(eve.id == regEntry.id){
					Event.registry.splice(key, 1);
					console.log(eve);
					proceed(eve.element, eve.name, eve.handler, eve.useCapture);
					return true;
				}
			});
		}
	),
	
	/**
	 * Uses the registry to stop opserving all events applied to an element
	 * Example:
	 * $('id').observe('click', myFunctionA);
	 * $('id').observe('click', myFunctionB);
	 * $('id').observe('mousedown', myFunctionC);
	 * $('id').observe('mouseup', myFunctionD);
	 * $('id').stopObservingAll('click'); // removes all attached click events
	 * $('id').stopObservingAll(); // removes all attached events
	 * 
	 * @param DOMElement element
	 * @param string eventName [optional]
	 * @return true
	 */
	stopObservingAll: Event.stopObserving.wrap(
		function(proceed, element, eventName) {
			if(typeof(eventName) == 'undefined') eventName = null;
			if(typeof(element) == 'undefined') element = null;
			Event.registry.each(function(eve){
				if (!eve ||
					(eventName != null && eve.name != eventName) ||
					(element != null && eve.element != element))
					return;
				Event.registry.splice(x, 1);
				proceed(eve.element, eve.name, eve.handler, eve.useCapture);
			});
			return true;
		}
	),
	
	/**
	 * Loops through the internal event registry and calls the observer function on the first matching event.
	 * Example:
	 * $('id').observe('click', myFunctionA);
	 * $('id').observe('click', myFunctionB);
	 * Event.fire('id', 'click'); // calls myFunctionA
	 *
	 * @param DOMElement element
	 * @param String name
	 * @return void
	 */
	fire: function(element, name) {
		element = $(element);
		//TODO: figure out why this is here
		Event.registry.each(function(e) {
			if(e.element == element && e.name == name){
				e.observer();
				throw $break;
			}
		});
	}
});

Element.addMethods({
	/**
	 * Extend the Element object with the new Event.observe function
	 * @see Event.observe
	 */
	observe: Event.observe,
	
	/**
	 * Extend the Element object with the new Event.stopObservingAll function
	 * @see Event.stopObservingAll
	 */
	stopObservingAll: Event.stopObservingAll
});
Object.extend(document, {
	/**
	 * Extend the document object with the new Event.observe function
	 * @see Event.observe
	 */
	observe: Element.Methods.observe.methodize(),
	
	/**
	 * Extend the Element object with the new Event.stopObservingAll function
	 * @see Event.stopObservingAll
	 */
	stopObservingAll: Element.Methods.stopObservingAll.methodize()
});

/* window
---------------------------------------*/
Object.extend(window, {
	/**
	 * Extend the alert function to properly alert objects
	 * @param mixed msg
	 * @return void
	 */
	shout: function(msg){
		//TODO: remove this altogether
		if(!this.num) this.num = 0;
		if(typeof(this.showAlerts) == 'undefined') this.showAlerts = true;
		if(!this.showAlerts){
			return false;
		}
		this.num++;
		if(this.num%10 == 0){
			this.showAlerts = confirm("Ok to Continue; Cancel to stop alerts until page refresh");
		}
		if(!this.showAlerts){
			return false;
		}
		if(msg instanceof Object){
			var html = msg+"\n--------------------\nKEY:VALUE\n--------------------\n";
			for(var key in msg){
				html += key + ":" + msg[key] + "\n";
			}
			alert(html);
		}else{
			alert(msg);
		}
	}
});

/* document.viewport
---------------------------------------*/
Object.extend(document.viewport, {
	/**
	 * Updated function from the prototype site to make it work properly
	 * @param void
	 * @return Array
	 */
	getDimensions: function() {
		var dimensions = { }; 
		$w('width height').each(function(d) { 
			var D = d.capitalize();
			if (Prototype.Browser.Opera || document.documentElement.clientHeight == 0 || // IE quirksmode 
			Prototype.Browser.Gecko && !(document.body.clientHeight == document.body.offsetHeight &&  
			document.body.clientHeight == document.body.scrollHeight)) // FF quirksmode 
				dimensions[d] = document.body['client' + D]; 
			else if (Prototype.Browser.WebKit) dimensions[d] = self['inner' + D]; 
			else dimensions[d] = document.documentElement['client' + D]; 
		}); 
		return dimensions; 
	}
});
